#define L_TRI_MESH_H
#include <llib.h>
using namespace llib;

#include "triMesh.h"

#include <vector>
using namespace std;

class TurbineSimulator2 : public Simulator {
private:
    const Float             _timeDelta;
    Int32                   _timeStepId;
    const Float             _angularVelocity;
    Boolean                 _pause;

    const Float             _turbineRadius;
    const Float             _turbineXoffset;
    const Int32             _turbineFanNum;
    const Float             _turbineFanSize;
    const Int32             _turbineFanResolution;

    TriMeshModel            _baseFanModel;
    vector<TriMeshModel>    _turbineLeft;
    vector<TriMeshModel>    _turbineRight;

    vector< vector<Uint8> > _turbineLeftCollide;
    vector< vector<Uint8> > _turbineRightCollide;

    void initBaseFanModel(Float fanSize, Int32 fanResolution)
    {
        Int32 vertexNum = (fanResolution + 1) * (fanResolution + 2) / 2;
        Int32 faceNum = fanResolution * fanResolution;

        vector<Vec3f> vertexList(vertexNum);
        Vec3f curVertex(Float(-fanSize/SQRT3),0.f,0.f);
        Float dx = Float(fanSize/fanResolution/2*SQRT3);
        Float dy = Float(fanSize/fanResolution/2);
        Float triSize = Float(fanSize/fanResolution);
		Int32 index = 0;
        FOR(h,0,fanResolution + 1) {
			Vec3f lineStart = curVertex;
			FOR(w,0,h+1) {
				vertexList[index++] = curVertex;
				curVertex.y += triSize;
			}
			curVertex = lineStart;
			curVertex.x += dx;
			curVertex.y -= dy;
        }

		vector<Vec3i> faceVertexIndexList(faceNum);
		index = 0;
		Int32 upId = 0, lowId = 1;
		Vec3i tmp;
		FOR(h,0,fanResolution) {
			FOR(w,0,h+1) {
				tmp = Vec3i(upId, lowId, lowId+1);
				faceVertexIndexList[index++] = tmp;
				++upId;
				++lowId;
			}
			lowId += 1;
		}
		upId = 1;
		lowId = 4;
		FOR(h,1,fanResolution) {
			FOR(w,0,h) {
				tmp = Vec3i(upId, lowId, upId+1);
				faceVertexIndexList[index++] = tmp;
				++upId;
				++lowId;
			}
			upId += 1;
			lowId += 2;
		}

		// vertex and face-vertex index ready, build model
		_baseFanModel.SetModelData(vertexList,faceVertexIndexList);
    }

    void updateTurbineModel()
    {
        Float fanAngle = Float(2 * PI / _turbineFanNum);
        Float curAngle = Float(_angularVelocity * _timeDelta * _timeStepId);
        // build left turbine
        for(Int32 F = 0; F < _turbineFanNum; ++F, curAngle += fanAngle) {
            // build fan using origin as center
            Float cosVal = cosf(curAngle);
            Float sinVal = sinf(curAngle);
            FOR(v,0,_turbineLeft[F].GetVertexNum()) {
                Vec3f& vertex = _turbineLeft[F].GetVertex(v);
                Float baseX = _baseFanModel.GetVertex(v).x;
                vertex.x = baseX * cosVal;
                vertex.z = baseX * sinVal;
            }
            // move fan on x axis to final position
            FOR(v,0,_turbineLeft[F].GetVertexNum()) {
                _turbineLeft[F].GetVertex(v).x -= _turbineXoffset;
            }
        }
        // build right turbine as XOZ plane mirror with left
        for(Int32 F = 0; F < _turbineFanNum; ++F) {
            _turbineRight[F] = _turbineLeft[F];
            FOR(v,0,_turbineRight[F].GetVertexNum()) {
                Vec3f& vertex = _turbineRight[F].GetVertex(v);
                vertex.x = -vertex.x;
            }
        }
    }

public:
    TurbineSimulator2() :
      _timeDelta            ( 0.05f ),
      _timeStepId           ( 0 ),
      _angularVelocity      ( 0.3f ),
      _pause                ( FALSE ),
      _turbineRadius        ( 5.f ),
      _turbineXoffset       ( 5.f ),
      _turbineFanNum        ( 1 ),
      _turbineFanSize       ( 2.f ),
      _turbineFanResolution ( 20 )
    {
        initBaseFanModel(_turbineFanSize, _turbineFanResolution);
        // move fan forward on x axis
        FOR(v,0,_baseFanModel.GetVertexNum()) {
            _baseFanModel.GetVertex(v).x += _turbineRadius;
        }

        // init left & right turbine model vector
        _turbineLeft = vector<TriMeshModel>(_turbineFanNum, _baseFanModel);
        _turbineRight = vector<TriMeshModel>(_turbineFanNum, _baseFanModel);
        Int32 faceNum = _turbineFanResolution * _turbineFanResolution;
        _turbineLeftCollide = vector< vector<Uint8> >(_turbineFanNum, vector<Uint8>(faceNum,0));
        _turbineRightCollide = vector< vector<Uint8> >(_turbineFanNum, vector<Uint8>(faceNum,0));
    }

    virtual Boolean Render()
    {
        const static Vec3f colorCollisionFree = Vec3f(0.2f,1.f,0.2f);
        const static Vec3f colorCollision = Vec3f(1.f,0.2f,0.2f);

        updateTurbineModel();

        if(!_pause) {
        // reset collision flag
        Int32 faceNum = _turbineFanResolution * _turbineFanResolution;
        FOR(I,0,_turbineFanNum) {
            FOR(J,0,faceNum) {
                _turbineLeftCollide[I][J] = 0;
                _turbineRightCollide[I][J] = 0;
            }
        }

        // let's collision check
        Tri3f leftTri, rightTri;
        FOR(l,0,_turbineFanNum) {
            TriMeshModel& leftFan = _turbineLeft[l];
            vector<Uint8>& leftCollide = _turbineLeftCollide[l];
            FOR(r,0,_turbineFanNum) {
                TriMeshModel& rightFan = _turbineRight[r];
                vector<Uint8>& rightCollide = _turbineRightCollide[r];
                FOR(triL,0,leftFan.GetFaceNum()) {
                    ModelFacePtr triLeft = leftFan.GetFace(triL);
                    leftTri[0] = triLeft->VertexA;
                    leftTri[1] = triLeft->VertexB;
                    leftTri[2] = triLeft->VertexC;
                    FOR(triR,0,rightFan.GetFaceNum()) {
                        ModelFacePtr triRight = rightFan.GetFace(triR);
                        rightTri[0] = triRight->VertexA;
                        rightTri[1] = triRight->VertexB;
                        rightTri[2] = triRight->VertexC;
                        if(llib::geom::TriPairTester::TestStatic(leftTri, rightTri)) {
                            leftCollide[triL] = 1;
                            rightCollide[triR] = 1;
                        }
                    }
                }
            }
        }
        }



        // draw left turbine
        glBegin(GL_TRIANGLES);
        FOR(t,0,SZ(_turbineLeft)) {
            TriMeshModel& turbineModel = _turbineLeft[t];
            FOR(f,0,turbineModel.GetFaceNum()) {
                if(_turbineLeftCollide[t][f] == 0) {
                    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, colorCollisionFree.cell);
                    glColor3fv(colorCollisionFree.cell);
                } else {
                    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, colorCollision.cell);
                    glColor3fv(colorCollision.cell);
                }

                ModelFacePtr face = turbineModel.GetFace(f);
                glVertex3fv(face->VertexA.cell);
                glVertex3fv(face->VertexB.cell);
                glVertex3fv(face->VertexC.cell);
            }
        }
        glEnd();

        // draw right turbine
        glBegin(GL_TRIANGLES);
        FOR(t,0,SZ(_turbineLeft)) {
            TriMeshModel& turbineModel = _turbineRight[t];
            FOR(f,0,turbineModel.GetFaceNum()) {
                if(_turbineRightCollide[t][f] == 0) {
                    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, colorCollisionFree.cell);
                    glColor3fv(colorCollisionFree.cell);
                } else {
                    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, colorCollision.cell);
                    glColor3fv(colorCollision.cell);
                }

                ModelFacePtr face = turbineModel.GetFace(f);
                glVertex3fv(face->VertexA.cell);
                glVertex3fv(face->VertexB.cell);
                glVertex3fv(face->VertexC.cell);
            }
        }
        glEnd();




        if(!_pause) ++_timeStepId;

        return TRUE;
    }


    virtual Boolean OnKeyDown(Uint8 key, Int32 x, Int32 y, Uint8 modifiers)
    {
        if(key == 32) { /*space*/
            _pause = !_pause;
        }
        return TRUE;
    }
};





Int32 main(Int32 argc, Char **argv)
{
    AppFramework.SetSimulator(new Navigator(new TurbineSimulator2()));
    AppFramework.SetWindowTitle("Turbine Simulator 2");
    AppFramework.SetFullScreen(FALSE);
    AppFramework.FrameworkMain(argc, argv);
    return 0;
}
